#include "net/EventLoop.hxx"
#include "net/EPoll.hxx"
#include "gtest/gtest.h"
#include "gmock/gmock.h"

using namespace kiwii::net;
using namespace std;

using ::testing::InSequence;
using ::testing::_;
using ::testing::Invoke;

class MockPollScheme {
public:
  MOCK_METHOD3(addEvent, void(int, unsigned int, unsigned int));
  MOCK_METHOD3(delEvent, void(int, unsigned int, unsigned int));
  MOCK_METHOD2(poll, void(struct timeval const*, vector<FiredIOEvent>&));
};

class PollSchemeDelegator {
public:
  void addEvent(int f, unsigned int n, unsigned int o) {
    mock_->addEvent(f, n, o);
  }

  void delEvent(int f, unsigned int n, unsigned int o) {
    mock_->delEvent(f, n, o);
  }

  void poll(struct timeval const* t, vector<FiredIOEvent>& v) {
    mock_->poll(t, v);
  }

  static MockPollScheme* mock_;
};

MockPollScheme* PollSchemeDelegator::mock_ = nullptr;

TEST(EventLoopTest, setIOEvent) {
  MockPollScheme mock;

  {
    InSequence dummy;

    EXPECT_CALL(mock, addEvent(1, EV_READABLE, EV_NONE));
    EXPECT_CALL(mock, addEvent(1, EV_WRITABLE, EV_READABLE));
  }

  EventLoop<PollSchemeDelegator> el;
  PollSchemeDelegator::mock_ = &mock;

  el.setIOEvent(1, EV_READABLE, 0);
  el.setIOEvent(1, EV_WRITABLE, 0);
}

TEST(EventLoopTest, delIOEvent) {
  MockPollScheme mock;

  {
    InSequence dummy;

    EXPECT_CALL(mock, addEvent(1, EV_READABLE | EV_WRITABLE, EV_NONE));
    EXPECT_CALL(mock, delEvent(1, EV_READABLE, EV_READABLE | EV_WRITABLE));
    EXPECT_CALL(mock, delEvent(1, EV_WRITABLE, EV_WRITABLE));
    EXPECT_CALL(mock, addEvent(1, EV_READABLE, EV_NONE));
    EXPECT_CALL(mock, delEvent(1, EV_READABLE, EV_READABLE));
    EXPECT_CALL(mock, delEvent(_, _, _))
      .Times(0);
  }

  EventLoop<PollSchemeDelegator> el;
  PollSchemeDelegator::mock_ = &mock;

  el.setIOEvent(1, EV_READABLE | EV_WRITABLE, 0);
  el.delIOEvent(1, EV_READABLE);
  el.delIOEvent(1, EV_WRITABLE);
  el.setIOEvent(1, EV_READABLE, EV_NONE);
  el.delIOEvent(1, EV_ALL);
  el.delIOEvent(1, EV_READABLE);
}

#include <unistd.h>
#include <sys/eventfd.h>
#include <functional>

using namespace std::placeholders;

class MockEventHandler {
public:
  MOCK_METHOD3(ioHandler, void(EventLoopBase&, int, unsigned int));
  MOCK_METHOD2(timeHandler, long long(EventLoopBase&, int));
};

class RockTest : public ::testing::Test {
public:
  RockTest() {
    TimeEvent::nextTimerId_ = 0; //we need to reset next timer id as it increments every time timer created.

    fd1_ = eventfd(0, EFD_NONBLOCK);
    fd2_ = eventfd(0, EFD_NONBLOCK);

    EXPECT_GT(fd1_, 0);
    EXPECT_GT(fd2_, 0);
  }

  ~RockTest() {
    close(fd1_);
    close(fd2_);
  }

  int fd1_;
  int fd2_;
};

TEST_F(RockTest, IOEvent) {
  uint64_t x = 1;

  MockEventHandler mock;

  EXPECT_CALL(mock, ioHandler(_, fd2_, EV_WRITABLE))
    .Times(2)
    .WillRepeatedly(Invoke([&x, &fd1_](EventLoopBase&, int, unsigned int) {
	  write(fd1_, &x, sizeof(x));
	}));

  EXPECT_CALL(mock, ioHandler(_, fd1_, EV_READABLE))
    .WillOnce(Invoke([&x](EventLoopBase& el, int fd, unsigned int){
	  read(fd, &x, sizeof(x));
	  el.stop();
	}));

  EventLoop<EPoll> el;
  el.setIOEvent(fd1_, EV_READABLE, bind(&MockEventHandler::ioHandler, &mock, _1, _2, _3));
  el.setIOEvent(fd2_, EV_WRITABLE, bind(&MockEventHandler::ioHandler, &mock, _1, _2, _3));

  el.rock();
}

TEST_F(RockTest, TimeEvent) {
  MockEventHandler mock;

  {
    InSequence dummy;
  
    EXPECT_CALL(mock, timeHandler(_, 0))
      .WillOnce(Invoke([](EventLoopBase&, int) {
	    return 2;
	  }))
      .RetiresOnSaturation();

    EXPECT_CALL(mock, timeHandler(_, 0))
      .WillOnce(Invoke([](EventLoopBase&, int) {
    	    return 0;
    	  }))
      .RetiresOnSaturation();

    EXPECT_CALL(mock, timeHandler(_, 1))
      .WillOnce(Invoke([&fd1_](EventLoopBase&, int) {
	    uint64_t x;
	    write(fd1_, &x, sizeof(x));
	    return 0;
	  }))
      .RetiresOnSaturation();

    EXPECT_CALL(mock, ioHandler(_, fd1_, _))
      .WillOnce(Invoke([](EventLoopBase& el, int, unsigned int){
	    el.stop();
	  }))
      .RetiresOnSaturation();
  }

  EventLoop<EPoll> el;
  el.setIOEvent(fd1_, EV_READABLE, bind(&MockEventHandler::ioHandler, &mock, _1, _2, _3));
  el.setTimeEvent(1, bind(&MockEventHandler::timeHandler, &mock, _1, _2));
  el.setTimeEvent(8, bind(&MockEventHandler::timeHandler, &mock, _1, _2));

  el.rock();
}

